package ae.pcfc.etks.online.common.util;

import java.io.File;
import java.io.StringReader;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

public class JAXBUtil {
	private static Logger log = LoggerFactory.getLogger(JAXBUtil.class);
	private String allPackageName = null;
	private Class jaxbClass = null;
	private JAXBContext jaxbContext = null;

	/**
	 * Utility method for converting Date to XMLGregorianCalendar
	 * 
	 * @param cal
	 * @return XMLGregorianCalendar
	 */
	public static XMLGregorianCalendar getXMLGregorianCalendar(Date date) {
		XMLGregorianCalendar xmlCalendar = null;
		try {
			DatatypeFactory dataTypeFactory;

			dataTypeFactory = DatatypeFactory.newInstance();
			GregorianCalendar cal = (GregorianCalendar) Calendar.getInstance();
			cal.setTime(date);
			xmlCalendar = dataTypeFactory.newXMLGregorianCalendar(cal);
		} catch (Exception e) {
			log.error("Problewms while getting XMLGregorianCalendar for date ["
					+ date + "]", e);
		}
		return xmlCalendar;
	}

	/**
	 * Utility method for parsing XMLGregorianCalendar into a Date.
	 * 
	 * @param cal
	 * @return Date
	 */
	public static Date parseXMLGregorianCalendar(
			XMLGregorianCalendar xmlCalendar) {
		if (xmlCalendar == null) {
			return null;
		} else {
			return xmlCalendar.toGregorianCalendar().getTime();
		}
	}

	/**
	 * Constructor to accept package name in String array
	 * 
	 * @param packageName
	 */
	public JAXBUtil(String[] packageName) {
		StringBuffer givenPackageName = new StringBuffer();

		for (int i = 0; i < packageName.length; i++) {
			givenPackageName.append(packageName[i]);

			if ((i + 1) < packageName.length) {
				givenPackageName.append(":");
			}
		}

		allPackageName = givenPackageName.toString();
	}

	/**
	 * 
	 * @param jaxbClass
	 */
	public JAXBUtil(Class jaxbClass) {
		this.jaxbClass = jaxbClass;
	}

	/**
	 * 
	 * @return
	 * @throws JAXBException
	 */
	protected JAXBContext getJAXBContext() throws JAXBException {
		if (jaxbContext == null) {
			if (jaxbClass != null) {
				jaxbContext = JAXBContext.newInstance(jaxbClass);
			} else {
				jaxbContext = JAXBContext.newInstance(allPackageName,
						getClass().getClassLoader());
			}
		}

		return jaxbContext;
	}

	/**
	 * 
	 * @param xmlString
	 * @return
	 * @throws JAXBUtilException
	 */
	public JAXBElement unMashallFromString(String xmlString)
			throws RuntimeException {
		if (xmlString == null) {
			throw new RuntimeException("XML value is null");
		}

		return unmashalFromString(xmlString);
	}

	/**
	 * 
	 * @param doc
	 * @return
	 * @throws JAXBUtilException
	 */
	public JAXBElement unMashallFromDocument(Document doc)
			throws RuntimeException {
		if (doc == null) {
			throw new RuntimeException("Document value is null");
		}

		JAXBElement jaxbElement = unmashalFromDocument(doc);

		return jaxbElement;
	}

	/**
	 * 
	 * @param fileName
	 * @return
	 * @throws JAXBUtilException
	 */
	public JAXBElement unMashallerRequest(String fileName)
			throws RuntimeException {
		if (fileName == null) {
			throw new RuntimeException("File name is null");
		}

		JAXBElement jaxbElement = null;

		try {
			JAXBContext jaxbContext = getJAXBContext();
			Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

			long start = System.currentTimeMillis();
			jaxbElement = (JAXBElement) unmarshaller.unmarshal(new File(
					fileName));
			long end = System.currentTimeMillis();
			if (log.isDebugEnabled()) {
				log.debug("Time to unmarshall xml: " + ((end - start) / 1000d)
						+ " secs");
				log.debug("Object Type: "
						+ jaxbElement.getDeclaredType().getCanonicalName());
			}
		} catch (JAXBException jaxbEx) {
			throw new RuntimeException("Error during unmarshall ", jaxbEx);
		}

		return jaxbElement;
	}

	/**
	 * 
	 * @param doc
	 * @return
	 * @throws JAXBUtilException
	 */
	private JAXBElement unmashalFromDocument(Document doc)
			throws RuntimeException {
		JAXBElement jaxbElement = null;

		try {
			JAXBContext jaxbContext = getJAXBContext();
			Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

			long start = System.currentTimeMillis();
			jaxbElement = (JAXBElement) unmarshaller.unmarshal(doc);
			long end = System.currentTimeMillis();
			if (log.isDebugEnabled()) {
				log.debug("Time to unmarshall xml: " + ((end - start) / 1000d)
						+ " secs");
				log.debug("Object Type: "
						+ jaxbElement.getDeclaredType().getCanonicalName());
			}
		} catch (JAXBException jaxbEx) {
			throw new RuntimeException("Error during unmarshall ", jaxbEx);
		}

		return jaxbElement;
	}

	/**
	 * 
	 * @param xmlString
	 * @return
	 * @throws JAXBUtilException
	 */
	private JAXBElement unmashalFromString(String xmlString)
			throws RuntimeException {
		JAXBElement jaxbElement = null;

		try {
			JAXBContext jaxbContext = getJAXBContext();
			Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

			long start = System.currentTimeMillis();
			jaxbElement = (JAXBElement) unmarshaller
					.unmarshal(new StringReader(xmlString));
			long end = System.currentTimeMillis();
			if (log.isDebugEnabled()) {
				log.debug("Time to unmarshall xml: " + ((end - start) / 1000d)
						+ " secs");
				log.debug("Object Type: "
						+ jaxbElement.getDeclaredType().getCanonicalName());
			}
		} catch (JAXBException jaxbEx) {
			throw new RuntimeException("Error during unmarshall ", jaxbEx);
		}

		return jaxbElement;
	}
}